package rs.creator;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import rs.domain.Track;

/* Classe responsabile di un duplice compito:
 * 		- calcolare, per ogni utente, la similarità con l'utente iniziale
 * 		- creare un vettore di tag pesato in base alla similarità di un utente */
public class SimilarityAndTagWeightCreator {
	
	private Map<String, Double> tags2weights;
	private Map<String, Double> user2similarity;
	private double euclidean_length;
	
	public SimilarityAndTagWeightCreator() {
		this.tags2weights = new HashMap<String, Double>();
		this.user2similarity = new HashMap<String, Double>();
	}

	/* Meotodo principale della classe */
	public void create(String user, Map<String, List<Track>> user2tracks, Map<Track, List<String>> track2tags) {
		/* Prima calcolo il vettore pesato dei tag dell'utente */
		Map<String, Integer> user_vector = this.getTagVector(user, user2tracks, track2tags);
		
		/* Poi calcolo la lunghezza euclidea di tale vettore (verrà utilizzata per il calcolo della similarità)*/
		for (String tag : user_vector.keySet()) {
			Integer w1 = user_vector.get(tag);
			this.euclidean_length += w1 * w1;
		}
		this.euclidean_length = Math.sqrt(this.euclidean_length);
		
		/* Infine, per ogni utente, calcolo 
		 * 		- la similarità con l'utente iniziale 
		 * 		- il contributo al peso dei tag */
		for (String u : user2tracks.keySet()) {
			Map<String, Integer> v = this.getTagVector(u, user2tracks, track2tags);
			double s = this.getSimilarity(user_vector, v);
			this.addWeight(s, v);
			this.user2similarity.put(u, s);
		}
	}

	/* Dato un utente, calcola il suo vettore pesato di tag.
	 * Per ogni tag di ogni brano dell'utente, si da un peso di +1 a quel tag. */
	private Map<String, Integer> getTagVector(String user,  Map<String, List<Track>> user2tracks, Map<Track, List<String>> track2tags) {
		Map<String, Integer> vector = new HashMap<String, Integer>();

		for (Track track : user2tracks.get(user)) {
			for (String tag : track2tags.get(track)) {
				if (!vector.containsKey(tag)) {
					vector.put(tag, 1);
				}
				else {
					vector.put(tag, vector.get(tag) + 1);
				}
			}
		}

		return vector;
	}

	/* Dati due vettori pesati di tag, relativi a due utenti, calcola la loro similarità.
	 * Per il calcolo viene utilizzata la cosine similarity */
	private double getSimilarity(Map<String, Integer> vector1, Map<String, Integer> vector2) {
		int num = 0;
		double den2 = 0;
		
		for (String tag : vector1.keySet()) {
			Integer w2 = vector2.get(tag);
			if (w2 != null) {
				Integer w1 = vector1.get(tag);
				num += w1 * w2;
			}
		}

		for (String tag : vector2.keySet()) {
			Integer w2 = vector2.get(tag);
			den2 += w2 * w2;
		}
		
		den2 = Math.sqrt(den2);
		
		return num / (this.euclidean_length * den2);
	}
	
	/* Aggiorna il peso dei tag, presi dal vettore di un utente, sulla base della sua similarità con l'utente iniziale. */
	private void addWeight(double similarity, Map<String, Integer> vector) {
		for (String tag : vector.keySet()) {
			if (!this.tags2weights.containsKey(tag)) {
				this.tags2weights.put(tag, vector.get(tag) * similarity);
			}
			else {
				this.tags2weights.put(tag, this.tags2weights.get(tag) + vector.get(tag) * similarity);
			}
		}
	}

	public Map<String, Double> getTagsMap() {
		return this.tags2weights;
	}

	public Map<String, Double> getUsersMap() {
		return this.user2similarity;
	}
	
}
